/*
 * Copyright 2011-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.mgmt.system.menu.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import me.ijleex.mgmt.commons.util.MathUtils;
import me.ijleex.mgmt.commons.util.snowflake.IdWorker;
import me.ijleex.mgmt.framework.constant.Constants;
import me.ijleex.mgmt.framework.entity.Result;
import me.ijleex.mgmt.system.menu.entity.Menu;
import me.ijleex.mgmt.system.menu.entity.UserMenu;
import me.ijleex.mgmt.system.menu.mapper.MenuMapper;
import me.ijleex.mgmt.system.menu.service.IMenuService;
import me.ijleex.mgmt.system.role.entity.Role;
import me.ijleex.mgmt.system.role.entity.RoleMenu;
import me.ijleex.mgmt.system.role.mapper.RoleMapper;
import me.ijleex.mgmt.system.role.mapper.RoleMenuMapper;

/**
 * 菜单 服务层实现
 *
 * @author liym
 * @since 2019-03-14 12:28:19 By 'codegen'
 */
@Service
public class MenuServiceImpl implements IMenuService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final MenuMapper mapper;

    private final RoleMapper roleMapper;
    private final RoleMenuMapper roleMenuMapper;

    public MenuServiceImpl(MenuMapper mapper, RoleMapper roleMapper, RoleMenuMapper roleMenuMapper) {
        this.mapper = mapper;
        this.roleMapper = roleMapper;
        this.roleMenuMapper = roleMenuMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result insertMenu(Menu menu) {
        // 判断要添加的菜单是否合法
        Long pid = menu.getPid();
        if (MathUtils.isNull(pid)) {
            return Result.invalidArg("上级菜单ID不能为空：pid");
        }

        final Menu rootMenu = this.findRootMenuById(pid);

        String systemId = menu.getSystemId();
        int type;
        if (!StringUtils.hasLength(systemId)) {
            if (rootMenu == null) {
                // 等于null，表示是一个新系统的起始菜单，则`系统标识符`不能为空
                return Result.error("系统标识符不能为空：systemId");
            }
            type = 1;
        } else {
            // 有项目起始菜单标识，判断是否合理
            if (rootMenu != null) {
                // 如果不等于null，则表示现在要添加的菜单是某一个项目的子菜单，这时，systemId 必须为空（，且type须设置为1）
                type = 1;
                menu.setSystemId(null);
            } else {
                String msg = "添加菜单失败：上级菜单「" + pid + "」的类型（type）与系统标识符（systemId）不正确。";
                return Result.error(msg);
            }
        }

        long id = IdWorker.getId();
        menu.setId(id);
        menu.setType(type);
        menu.setState(0);
        try {
            int x = this.mapper.insert(menu);
            if (x == 0) {
                return Result.error("添加菜单失败");
            } else {
                // 添加成功，将上级菜单的类型修改为“分支菜单”
                Menu parentMenu = this.mapper.selectById(pid);
                if (parentMenu.getType() == 1) {
                    // 如果某个菜单是叶子菜单，在其下面成功添加一个菜单之后，将它的类型修改为2（分支菜单）
                    /// parentMenu.setUri(null);
                    parentMenu.setType(2);
                    int y = this.mapper.update(parentMenu);
                    if (y == 0) {
                        String msg = "添加菜单成功，但修改上级菜单「" + pid + "」的类型为〔分支菜单〕时失败，请检查。";
                        this.logger.warn(msg);
                        return Result.error(msg);
                    } else {
                        return Result.ok("添加菜单成功：" + id);
                    }
                } else {
                    return Result.ok("添加菜单成功：" + id);
                }
            }
        } catch (Exception e) {
            this.logger.error("insertMenu error", e);
            throw e;
        }
    }

    /**
     * 根据指定的菜单节点，查找它的根节点
     *
     * <p>菜单根节点的特征是类型（type）为 2，系统标识符（systemId）不为空</p>
     *
     * @param menuId 菜单 ID
     * @return 指定菜单的根节点
     * @version 2019-03-20 11:03
     * @since 2016-08-24 11:04:32
     */
    private Menu findRootMenuById(Long menuId) {
        List<Menu> menuList = this.mapper.selectParentMenuById(menuId);
        for (Menu menu : menuList) {
            Integer type = menu.getType();
            String systemId = menu.getSystemId();
            if (type == 2 && StringUtils.hasLength(systemId)) {
                return menu;
            }
        }
        return null;
    }

    @Override
    public Result deleteMenuByIds(String ids) {
        if (!StringUtils.hasText(ids)) {
            return Result.invalidArg("要删除的ID不能为空");
        }
        if (ids.contains(Constants.DELIMITER)) {
            int x = this.mapper.delete(ids);
            return Result.of(x);
        } else {
            return this.deleteMenu(Long.parseLong(ids));
        }
    }

    /**
     * 根据 ID 删除菜单
     *
     * @param menuId 菜单 ID
     * @return 成功与失败的信息
     * @since 2019-03-20 11:43
     */
    private Result deleteMenu(Long menuId) {
        Menu menu = this.mapper.selectById(menuId);
        if (menu == null) {
            return Result.invalidArg("要删除的菜单不存在：" + menuId);
        }

        // 删除菜单前，判断该节点是否有子菜单，如果有，则不可删除。
        List<Menu> subMenuList = this.mapper.selectSubMenuById(menuId);
        if (!subMenuList.isEmpty()) {
            return Result.error("要删除的菜单有下级菜单，不能删除。");
        }

        List<RoleMenu> roleMenuList = this.roleMenuMapper.selectList(new RoleMenu(null, menuId));
        if (!roleMenuList.isEmpty()) {
            // 授权时（RoleMenuService#grant 方法），请设置 remarks 的值为“角色名称”
            RoleMenu roleMenu = roleMenuList.get(0);
            String remarks = roleMenu.getRemarks();
            return Result.error("你选中的菜单与角色【" + remarks + "】有关联，不能删除！");
        }

        int x = this.mapper.deleteById(menuId);
        if (x == 0) {
            return Result.error("删除菜单失败");
        } else {
            Long pid = menu.getPid();
            subMenuList = this.mapper.selectSubMenuById(pid);
            if (subMenuList.isEmpty()) {
                this.logger.debug("菜单【{}】的最后一个菜单已被删除，设置其类型为 2-分支菜单。", pid);
                Menu updateMenu = new Menu(pid);
                updateMenu.setType(1);
                this.mapper.update(updateMenu);
            }
            return Result.ok(menuId);
        }
    }

    @Override
    public Result updateMenu(Menu menu) {
        int x = this.mapper.update(menu);
        return Result.of(x);
    }

    @Override
    public List<Menu> getMenuList(Menu menu) {
        return this.mapper.selectList(menu);
    }

    @Override
    public Menu getMenuById(Long id) {
        return this.mapper.selectById(id);
    }

    /**
     * @since 2019-03-15 11:24
     */
    @Override
    public Result getMenuTreeByUser(Long userId, String systemId) {
        if (MathUtils.isNullOrZero(userId)) {
            return Result.invalidArg("用户ID不能为空：userId");
        }
        if (!StringUtils.hasLength(systemId)) {
            return Result.invalidArg("系统标识符不能为空：systemId");
        }

        Menu rootMenu = this.mapper.selectRootMenuByUser(new UserMenu(userId, systemId));
        if (rootMenu == null) {
            return Result.error("当前用户不能访问该系统：" + systemId);
        }

        // 查询已授权给指定用户的菜单的列表 2016-08-15 14:57
        List<Long> myMenuIds = this.mapper.selectMenuIdByUserId(userId);

        this.expandMenuTree(rootMenu, myMenuIds);
        return Result.ok(rootMenu);
    }

    /**
     * 根据根菜单、已授权的菜单的 ID，扩展菜单树（即填充子菜单）
     *
     * <p>用于获取与用户相关的菜单</p>
     *
     * @param menu 根菜单，即一个项目的起始菜单
     * @param authorizedMenuIds 已授权的菜单的id
     * @since 2019-03-15 12:35
     * @since 2016-01-26 22:30
     */
    private void expandMenuTree(Menu menu, List<Long> authorizedMenuIds) {
        Long menuId = menu.getId();
        if (MathUtils.isNullOrZero(menuId)) {
            return;
        }

        List<Menu> subMenuList = this.mapper.selectSubMenuById(menuId);

        List<Menu> children = new ArrayList<>(subMenuList.size());
        for (Menu node : subMenuList) {
            Long nodeId = node.getId();
            if (authorizedMenuIds.contains(nodeId)) {
                Integer type = node.getType();
                if (type == 1 || type == 3) {
                    children.add(node);
                } else if (type == 2) {
                    this.expandMenuTree(node, authorizedMenuIds);
                    children.add(node);
                }
            }
        }
        menu.setChildren(children);
    }

    /**
     * @since 2019-03-15 11:24
     */
    @Override
    public Result getMenuTreeByRole(Long roleId) {
        if (MathUtils.isNullOrZero(roleId)) {
            return Result.invalidArg("角色id不能为空");
        }

        Role role = this.roleMapper.selectById(roleId);
        if (role == null) {
            return Result.error("角色不存在：" + roleId);
        }

        //  查询已授权给指定角色的菜单的列表 2016-08-23 16:59
        List<Long> myMenuIds = this.roleMenuMapper.selectMenuIdByRoleId(roleId);

        Menu menu = new Menu(0L);
        this.expandMenuTree(menu, myMenuIds, -1);

        List<Menu> children = menu.getChildren();
        menu = children.isEmpty() ? menu : children.get(0);
        return Result.ok(menu);
    }

    /**
     * 根据菜单id，扩展菜单树（即填充子菜单）
     *
     * @param menu 要扩展的菜单
     * @param checkedMenuIds 选中的菜单的 ID 列表，如果其中包含 {@code menu} 的 ID，则将 menu 的 checked 属性设为 true
     * @param openDepth 展开菜单的深度
     * @version 2019-03-15 15:05
     * @since 2016-01-29 16:58:33
     */
    private void expandMenuTree(Menu menu, List<Long> checkedMenuIds, int openDepth) {
        Long menuId = menu.getId();
        if (MathUtils.isNull(menuId)) {
            menuId = 0L;
            menu.setId(menuId);
        }

        List<Menu> subMenuList = this.mapper.selectSubMenuById(menuId);

        List<Menu> children = new ArrayList<>(subMenuList.size());
        for (Menu node : subMenuList) {
            Long nodeId = node.getId();
            if (checkedMenuIds.contains(nodeId)) {
                node.setChecked(true);
            }
            if (openDepth < 2) {
                node.setOpen(true);
            }

            Integer type = node.getType();
            if (type == 1 || type == 3) {
                children.add(node);
            } else if (type == 2) {
                this.expandMenuTree(node, checkedMenuIds, openDepth + 1);
                children.add(node);
            }
        }
        menu.setChildren(children);
    }

    /**
     * @since 2019-03-15 11:24
     */
    @Override
    public Result getMenuTree(Menu object) {
        List<Long> emptyCheckedIds = Collections.emptyList();

        List<Menu> menuList = this.mapper.selectList(object);
        if (menuList.isEmpty()) {
            Menu menu = new Menu(0L);
            this.expandMenuTree(menu, emptyCheckedIds, 0);

            List<Menu> children = menu.getChildren();
            if (children.isEmpty()) {
                return Result.error("该菜单不存在或该菜单不存在子菜单，不能获取菜单树。");
            } else {
                menu = children.get(0);
                return Result.ok(menu);
            }
        } else {
            Menu menu = menuList.get(0);
            this.expandMenuTree(menu, emptyCheckedIds, 0);

            // 将最上面的菜单设为展开 2017-02-16 10:52:07
            menu.setOpen(true);
            return Result.ok(menu);
        }
    }

}
